{
 "cells": [
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Tutorial 5 - Run experiments"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In [Tutorial 4](./tutorial-4-setting-parameter-values.ipynb) we saw how to change the parameters, including the applied current. However, in some cases we might want to prescribe a given voltage, a given power or switch between different conditions to simulate experimental setups. We can use the Experiment class for these simulations."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Note: you may need to restart the kernel to use updated packages.\n"
     ]
    }
   ],
   "source": [
    "%pip install \"pybamm[plot,cite]\" -q    # install PyBaMM if it is not installed\n",
    "import numpy as np\n",
    "\n",
    "import pybamm"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## String-based instructions"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We start defining an experiment, which consists on a set of instructions on how to cycle the battery. These instructions can be defined in two different ways, but the simplest is to use strings. The instructions can be of the form `\"(Dis)charge at x A/C/W\"`, `\"Rest\"`, or `\"Hold at x V\"`. The instructions can also include how long each step should run for. The duration is introduced by the word `\"for\"` followed by the duration, e.g. `\"for 10 seconds\"`, `\"for 3 minutes\"` or `\"for 1 hour\"`. Terminating conditions can also be specified. In this case, the step will stop when that condition is met. These conditions should be a circuit state and are introduced by the word `\"until\"`, e.g. `\"until 1 A\"`, `\"until C/50\"` or `\"until 3 V\"`. Duration and termination conditions can be combined using the word `\"or\"` and the step will either finish when the condition is met or after the specified duration (whichever happens first).\n",
    "\n",
    "Some examples of experiment instructions are:\n",
    "```python\n",
    "    \"Discharge at 1C for 0.5 hours\",\n",
    "    \"Discharge at C/20 for 0.5 hours\",\n",
    "    \"Charge at 0.5 C for 45 minutes\",\n",
    "    \"Discharge at 1 A for 90 seconds\",\n",
    "    \"Charge at 200mA for 45 minutes\",\n",
    "    \"Discharge at 1 W for 0.5 hours\",\n",
    "    \"Charge at 200 mW for 45 minutes\",\n",
    "    \"Rest for 10 minutes\",\n",
    "    \"Hold at 1 V for 20 seconds\",\n",
    "    \"Charge at 1 C until 4.1V\",\n",
    "    \"Hold at 4.1 V until 50 mA\",\n",
    "    \"Hold at 3V until C/50\",\n",
    "```\n",
    "\n",
    "These steps can be concatenated in a list, so they are executed sequentially. To create an experiment, the list can then be passed when creating an `Experiment` object:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "experiment = pybamm.Experiment(\n",
    "    [\n",
    "        \"Discharge at C/10 for 10 hours or until 3.3 V\",\n",
    "        \"Rest for 1 hour\",\n",
    "        \"Charge at 1 A until 4.1 V\",\n",
    "        \"Hold at 4.1 V until 50 mA\",\n",
    "        \"Rest for 1 hour\",\n",
    "    ]\n",
    ")"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In order to reproduce real cycling conditions, often the experiments will be composed of several \"cycles\", where a cycle is a user-defined collection of steps. In PyBaMM, we can define a cycle as a tuple of steps, which means that we can process the solution in terms of cycles. For more information on this functionality, please see the [long experiments notebook](../simulations_and_experiments/simulating-long-experiments.ipynb). We can also leverage the list addition and multiplication operators to combine and repeat cycles. For example, if we want a three cycles of constant current C/10 discharge, a one hour rest, a constant current (1 A) constant voltage (4.1 V) and another one hour rest, followed by a cycle of 1C discharge we can write:\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "experiment = pybamm.Experiment(\n",
    "    [\n",
    "        (\n",
    "            \"Discharge at C/10 for 10 hours or until 3.3 V\",\n",
    "            \"Rest for 1 hour\",\n",
    "            \"Charge at 1 A until 4.1 V\",\n",
    "            \"Hold at 4.1 V until 50 mA\",\n",
    "            \"Rest for 1 hour\",\n",
    "        )\n",
    "    ]\n",
    "    * 3\n",
    "    + [\n",
    "        \"Discharge at 1C until 3.3 V\",\n",
    "    ]\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Note that if a cycle is made of one step only (like the 1C discharge) we do not need to define it as a tuple. One key difference between cycles and steps, is that PyBaMM allows for steps to be skipped (e.g. if you try to charge an a fully charged battery) but not cycles.\n",
    "\n",
    "Then we can choose our model and create our simulation, passing our experiment using a keyword argument"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "model = pybamm.lithium_ion.DFN()\n",
    "sim = pybamm.Simulation(model, experiment=experiment)"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We then solve and plot the solution"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "At t = 339.952 and h = 1.4337e-18, the corrector convergence failed repeatedly or with |h| = hmin.\n",
      "At t = 522.687 and h = 4.04917e-14, the corrector convergence failed repeatedly or with |h| = hmin.\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "93feca98298f4111909ae487e2a1e273",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "interactive(children=(FloatSlider(value=0.0, description='t', max=40.13268704803602, step=0.4013268704803602),…"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/plain": [
       "<pybamm.plotting.quick_plot.QuickPlot at 0x16520e690>"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "sim.solve()\n",
    "sim.plot()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The `solution` variable in the `simulation` object has a `cycles` variable that allows to access the solution for a specific cycle. That solution can be processed and plotted as usual. For example, if we want to plot the first cycle only we can do "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "4d6e43032f4e4aa6be5843c4916b4b50",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "interactive(children=(FloatSlider(value=0.0, description='t', max=13.076887099589111, step=0.1307688709958911)…"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/plain": [
       "<pybamm.plotting.quick_plot.QuickPlot at 0x1696cf290>"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "sim.solution.cycles[0].plot()"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Note that because `sol.cycles` is a list, the indexing starts at 0.\n",
    "\n",
    "As we will see in the next section, we can pass additional arguments such as a period, temperature, or tags when defining a step. The method `pybamm.step.string` can be used to add these additional conditions to a string-defined step:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Step(1.0, duration=1 hour, period=1 minute, temperature=25oC, tags=['tag1'], description=Discharge at 1C for 1 hour, direction=Discharge)"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "pybamm.step.string(\n",
    "    \"Discharge at 1C for 1 hour\", period=\"1 minute\", temperature=\"25oC\", tags=[\"tag1\"]\n",
    ")"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Direct instructions"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Experiments can also be specified programmatically without having to use string formatting. For example,"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Step(1, duration=1 hour, termination=2.5 V, direction=Discharge)"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "pybamm.step.current(1, duration=\"1 hour\", termination=\"2.5 V\")"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "is equivalent to "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Step(1.0, duration=1 hour, termination=2.5V, description=Discharge at 1A for 1 hour or until 2.5V, direction=Discharge)"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "pybamm.step.string(\"Discharge at 1A for 1 hour or until 2.5V\")"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The available methods are `current`, `c_rate`, `voltage`, `power`, and `resistance`. These methods also take optional keyword arguments, such as the period, temperature, tags or starting times (a complete list can be found in [the documentation](https://docs.pybamm.org/en/stable/source/api/experiment/experiment_steps.html)).\n",
    "\n",
    "These methods can also be used for drive cycles. In this case, the `value` argument should be a 2-column array, where the first column is time in seconds (should start at zero) and the second column the values (i.e. current, voltage, power...). Here is an example for a synthetically defined drive cycle:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "2024-07-10 14:41:02.625 - [WARNING] callbacks.on_experiment_infeasible_time(240): \n",
      "\n",
      "\tExperiment is infeasible: default duration (1.0 seconds) was reached during 'Step([[ 0.00000000e+00  0.00000000e+00]\n",
      " [ 1.69491525e-02  5.31467428e-02]\n",
      " [ 3.38983051e-02  1.05691312e-01]\n",
      " [ 5.08474576e-02  1.57038356e-01]\n",
      " [ 6.77966102e-02  2.06606093e-01]\n",
      " [ 8.47457627e-02  2.53832900e-01]\n",
      " [ 1.01694915e-01  2.98183679e-01]\n",
      " [ 1.18644068e-01  3.39155918e-01]\n",
      " [ 1.35593220e-01  3.76285385e-01]\n",
      " [ 1.52542373e-01  4.09151388e-01]\n",
      " [ 1.69491525e-01  4.37381542e-01]\n",
      " [ 1.86440678e-01  4.60655989e-01]\n",
      " [ 2.03389831e-01  4.78711019e-01]\n",
      " [ 2.20338983e-01  4.91342062e-01]\n",
      " [ 2.37288136e-01  4.98406004e-01]\n",
      " [ 2.54237288e-01  4.99822806e-01]\n",
      " [ 2.71186441e-01  4.95576416e-01]\n",
      " [ 2.88135593e-01  4.85714947e-01]\n",
      " [ 3.05084746e-01  4.70350133e-01]\n",
      " [ 3.22033898e-01  4.49656065e-01]\n",
      " [ 3.38983051e-01  4.23867214e-01]\n",
      " [ 3.55932203e-01  3.93275778e-01]\n",
      " [ 3.72881356e-01  3.58228370e-01]\n",
      " [ 3.89830508e-01  3.19122092e-01]\n",
      " [ 4.06779661e-01  2.76400033e-01]\n",
      " [ 4.23728814e-01  2.30546251e-01]\n",
      " [ 4.40677966e-01  1.82080288e-01]\n",
      " [ 4.57627119e-01  1.31551282e-01]\n",
      " [ 4.74576271e-01  7.95317480e-02]\n",
      " [ 4.91525424e-01  2.66110874e-02]\n",
      " [ 5.08474576e-01 -2.66110874e-02]\n",
      " [ 5.25423729e-01 -7.95317480e-02]\n",
      " [ 5.42372881e-01 -1.31551282e-01]\n",
      " [ 5.59322034e-01 -1.82080288e-01]\n",
      " [ 5.76271186e-01 -2.30546251e-01]\n",
      " [ 5.93220339e-01 -2.76400033e-01]\n",
      " [ 6.10169492e-01 -3.19122092e-01]\n",
      " [ 6.27118644e-01 -3.58228370e-01]\n",
      " [ 6.44067797e-01 -3.93275778e-01]\n",
      " [ 6.61016949e-01 -4.23867214e-01]\n",
      " [ 6.77966102e-01 -4.49656065e-01]\n",
      " [ 6.94915254e-01 -4.70350133e-01]\n",
      " [ 7.11864407e-01 -4.85714947e-01]\n",
      " [ 7.28813559e-01 -4.95576416e-01]\n",
      " [ 7.45762712e-01 -4.99822806e-01]\n",
      " [ 7.62711864e-01 -4.98406004e-01]\n",
      " [ 7.79661017e-01 -4.91342062e-01]\n",
      " [ 7.96610169e-01 -4.78711019e-01]\n",
      " [ 8.13559322e-01 -4.60655989e-01]\n",
      " [ 8.30508475e-01 -4.37381542e-01]\n",
      " [ 8.47457627e-01 -4.09151388e-01]\n",
      " [ 8.64406780e-01 -3.76285385e-01]\n",
      " [ 8.81355932e-01 -3.39155918e-01]\n",
      " [ 8.98305085e-01 -2.98183679e-01]\n",
      " [ 9.15254237e-01 -2.53832900e-01]\n",
      " [ 9.32203390e-01 -2.06606093e-01]\n",
      " [ 9.49152542e-01 -1.57038356e-01]\n",
      " [ 9.66101695e-01 -1.05691312e-01]\n",
      " [ 9.83050847e-01 -5.31467428e-02]\n",
      " [ 1.00000000e+00 -1.22464680e-16]], duration=1.0, period=0.016949152542372836, direction=Rest)'. The returned solution only contains up to step 1 of cycle 1. Please specify a duration in the step instructions.\n"
     ]
    },
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "6364b4579fc447e2a607f2f8414172ba",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "interactive(children=(FloatSlider(value=0.0, description='t', max=1.0, step=0.01), Output()), _dom_classes=('w…"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/plain": [
       "<pybamm.plotting.quick_plot.QuickPlot at 0x16a1d8d90>"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "t = np.linspace(0, 1, 60)\n",
    "sin_t = 0.5 * np.sin(2 * np.pi * t)\n",
    "drive_cycle_power = np.column_stack([t, sin_t])\n",
    "experiment = pybamm.Experiment([pybamm.step.power(drive_cycle_power)])\n",
    "sim = pybamm.Simulation(model, experiment=experiment)\n",
    "sim.solve()\n",
    "sim.plot()"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "For a drive cycle, the duration is until the final time provided and the period is the smallest time step. For best results, we recommend using a constant time step size."
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In this notebook we have seen how to use the Experiment class to run simulations of more complex operating conditions. In [Tutorial 6](./tutorial-6-managing-simulation-outputs.ipynb) we will see how to manage the outputs of the simulation."
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## References\n",
    "\n",
    "The relevant papers for this notebook are:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[1] Joel A. E. Andersson, Joris Gillis, Greg Horn, James B. Rawlings, and Moritz Diehl. CasADi – A software framework for nonlinear optimization and optimal control. Mathematical Programming Computation, 11(1):1–36, 2019. doi:10.1007/s12532-018-0139-4.\n",
      "[2] Von DAG Bruggeman. Berechnung verschiedener physikalischer konstanten von heterogenen substanzen. i. dielektrizitätskonstanten und leitfähigkeiten der mischkörper aus isotropen substanzen. Annalen der physik, 416(7):636–664, 1935.\n",
      "[3] Marc Doyle, Thomas F. Fuller, and John Newman. Modeling of galvanostatic charge and discharge of the lithium/polymer/insertion cell. Journal of the Electrochemical society, 140(6):1526–1533, 1993. doi:10.1149/1.2221597.\n",
      "[4] Charles R. Harris, K. Jarrod Millman, Stéfan J. van der Walt, Ralf Gommers, Pauli Virtanen, David Cournapeau, Eric Wieser, Julian Taylor, Sebastian Berg, Nathaniel J. Smith, and others. Array programming with NumPy. Nature, 585(7825):357–362, 2020. doi:10.1038/s41586-020-2649-2.\n",
      "[5] Scott G. Marquis, Valentin Sulzer, Robert Timms, Colin P. Please, and S. Jon Chapman. An asymptotic derivation of a single particle model with electrolyte. Journal of The Electrochemical Society, 166(15):A3693–A3706, 2019. doi:10.1149/2.0341915jes.\n",
      "[6] Peyman Mohtat, Suhak Lee, Jason B Siegel, and Anna G Stefanopoulou. Towards better estimability of electrode-specific state of health: decoding the cell expansion. Journal of Power Sources, 427:101–111, 2019.\n",
      "[7] Valentin Sulzer, Scott G. Marquis, Robert Timms, Martin Robinson, and S. Jon Chapman. Python Battery Mathematical Modelling (PyBaMM). Journal of Open Research Software, 9(1):14, 2021. doi:10.5334/jors.309.\n",
      "[8] Pauli Virtanen, Ralf Gommers, Travis E. Oliphant, Matt Haberland, Tyler Reddy, David Cournapeau, Evgeni Burovski, Pearu Peterson, Warren Weckesser, Jonathan Bright, and others. SciPy 1.0: fundamental algorithms for scientific computing in Python. Nature Methods, 17(3):261–272, 2020. doi:10.1038/s41592-019-0686-2.\n",
      "[9] Andrew Weng, Jason B Siegel, and Anna Stefanopoulou. Differential voltage analysis for battery manufacturing process control. arXiv preprint arXiv:2303.07088, 2023.\n",
      "\n"
     ]
    }
   ],
   "source": [
    "pybamm.print_citations()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.6"
  },
  "toc": {
   "base_numbering": 1,
   "nav_menu": {},
   "number_sections": true,
   "sideBar": true,
   "skip_h1_title": false,
   "title_cell": "Table of Contents",
   "title_sidebar": "Contents",
   "toc_cell": false,
   "toc_position": {},
   "toc_section_display": true,
   "toc_window_display": true
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
